package student.world;

import java.util.Random;

public class Critter {

	private CritterWorld world;
	private Hex hex;
	/*
	 * mem[0] is memory length (immutable).
	 * mem[1] is defense (immutable).
	 * mem[2] is offense (immutable).
	 * mem[3] is size.
	 * mem[4] is energy.
	 * mem[5] is appearance.
	 * mem[6] is rule counter.
	 */
	private int[] mem;
	private int dir; // this Critter's direction, 0..5, starting facing north and going clockwise.
	private boolean hasActed; // has acted during this timestep.
	private boolean isMating; // is mating during this timestep.
	
	// Critter command constants - used when controlling a Critter manually
	public static final int REST		= 0;
	public static final int FORWARD		= 1;
	public static final int BACKWARD	= 2;
	public static final int LEFT 		= 3;
	public static final int RIGHT		= 4;
	public static final int EAT			= 5;
	public static final int ATTACK		= 6;
	public static final int GROW		= 7;
	public static final int BUD 		= 8;
	public static final int MATE 		= 9;
	
	/**
	 * Returns the value of this Critter's memory at the specified index.
	 * @throws IllegalArgumentException if index is not a valid index in this Critter's mem array.
	 */
	public int getMemValue(int index) {
		if (index >= mem.length || index < 0)
			throw new IllegalArgumentException("This Critter does not have a memory value at the specified index.");
		return mem[index];
	}
	
	public int getDirection(){
		return dir;
	}
	
	//TODO some sort of functionality for controlling the Critter with a program will be needed
	/**
	 * Creates a new Critter
	 * Checks: given Hex is not occupied and memLength is >= 7.
	 * @param loc the Hex that contains this Critter
	 * @param memLength
	 * @param defense
	 * @param offense
	 * @param appearance
	 * @throws IllegalArgumentException if the given Hex is occupied (by a Critter or Rock)
	 */
	public Critter(CritterWorld w, Hex h, int memLength, int defense, int offense){
		if(h.isOccupied())
			throw new IllegalArgumentException("Cannot create a Critter in an occupied Hex.");
		if(memLength < 7)
			throw new IllegalArgumentException("Cannot create a Critter with memLength less than seven.");
		world = w;
		hex = h;
		hex.putCritter(this);
		mem = new int[memLength];
		mem[0] = memLength;
		mem[1] = defense;
		mem[2] = offense;
		mem[3] = 1;
		mem[4] = world.INITIAL_ENERGY;
		//TODO fix rule counter, appearance.
		mem[6] = 1;
		dir = (int)(Math.random()*6);//between 0 and 5
		world.putCritter(this);
	}
	
	/**
	 * Returns the Hex this Critter is in
	 */
	public Hex getHex(){
		return hex;
	}
	
	/**
	 * Moves this Critter onto the Hex in the direction given. Uses energy.
	 * @return true if move was successful.
	 */
	private boolean move(int direction) {
		if (useEnergy(world.MOVE_COST * mem[3])) return false;
		Location newLoc = hex.getLocation().nextLocation(direction);
		if (!newLoc.isValid())
			return false;
		Hex inFront = world.getHex(newLoc);
		if (inFront.isOccupied())
			return false;
		hex.removeCritter();
		hex = inFront;
		hex.putCritter(this);
		return true;
	}

	/**
	 * Moves this Critter onto the Hex behind it if possible. Uses energy.
	 * @return true if move was successful.
	 */
	public boolean backward() {
		return move((dir + 3)%6);
	}
	
	/**
	 * Moves this Critter onto the Hex in front of it if possible. Uses energy.
	 * @return true if move was successful.
	 */
	public boolean forward() {
		return move(dir);
	}
	
	/**
	 * This Critter does nothing except for use energy.
	 */
	public void rest() {
		useEnergy(mem[3]);
	}
	
	/**
	 * This Critter turns counterclockwise. Uses energy.
	 */
	public void left() {
		useEnergy(mem[3]);
		dir = (dir + 5) % 6;
	}
	
	/**
	 * This Critter turns clockwise. Uses energy.
	 */
	public void right() {
		useEnergy(mem[3]);
		dir = (dir + 1) % 6;
	}
	
	/**
	 * This Critter eats all the food and plant (if any) on its Hex. Uses energy.
	 */
	public void eat() {
		if (!useEnergy(mem[3])) {
			mem[4] += (hex.foodEaten() + hex.plantEaten());
			if (mem[4] > mem[3] * world.ENERGY_PER_SIZE)
				mem[4] = mem[3] * world.ENERGY_PER_SIZE;
		}
	}
	
	/**
	 * This Critter attacks the Critter in front of it if there is one. Uses energy.
	 * @return true if a Critter was attacked.
	 */
	public boolean attack() {
		if (useEnergy(world.ATTACK_COST * mem[3]))
			return false;
		Location inFront = hex.getLocation().nextLocation(dir);
		if (!inFront.isValid())
			return false;
		Hex forwardHex = world.getHex(inFront);
		if (!forwardHex.hasCritter())
			return false;
		Critter attacked = forwardHex.getCritter();
		double logParam = world.DAMAGE_INC * (mem[3]*mem[2] - attacked.mem[3]*attacked.mem[1]);
		double logistic = 1 / (1 + Math.pow(Math.E, (-1 * logParam)));
		int damage = (int)(world.BASE_DAMAGE * mem[3] * logistic);
		attacked.useEnergy(damage);
		return true;
	}
	
	/**
	 * This Critter increases in size if possible. Uses energy.
	 */
	public void grow() {
		if (!useEnergy(world.GROW_COST * mem[3] * complexity())) {
			mem[3]++;
		}
	}
	
	/**
	 * If possible, this Critter buds and the new Critter is placed behind it.
	 * @return true if this Critter buds successfully.
	 */
	public boolean bud() {
		if (useEnergy(world.BUD_COST * complexity()))
			return false;
		Location baby = hex.getLocation().nextLocation((dir + 3)%6);
		if (!baby.isValid())
			return false;
		Hex babyCrib = world.getHex(baby);
		if (babyCrib.isOccupied())
			return false;
		babyCrib.putCritter(new Critter(world, babyCrib, mem[0], mem[1], mem[2]));
		return true;
	}
	
	/**
	 * Tries to mate with a Critter in front of it. Uses energy.
	 * Can only mate if the other Critter is facing it and trying to mate.
	 * If the other Critter has not acted yet, this Critter does not make a baby,
	 * but this Critter changes its mating status to true.
	 * @return true if this Critter makes a baby.
	 */
	public boolean mate() {
		Location inFront = hex.getLocation().nextLocation(dir);
		if (!inFront.isValid())
			return false;
		Hex partnerHex = world.getHex(inFront);
		if (partnerHex.isRock() || !partnerHex.hasCritter())
			return false;
		Critter partner = partnerHex.getCritter();
		if (partner.dir != (dir + 3)%6)
			return false;
		if (!partner.hasActed) {
			isMating = true;
			hasActed = true;
			return false;
		}
		if (!partner.isMating)
			return false;
		Random rnd = new Random();
		int randomHex = rnd.nextInt(2);
		if (randomHex == 0)
			return makeBaby(partner);
		else
			return partner.makeBaby(this);
	}
	
	/**
	 * Calculates the complexity of this Critter.
	 * @return the complexity.
	 */
	private int complexity() {
		return mem[6] * world.RULE_COST + (mem[1] + mem[2]) * world.ABILITY_COST;
	}
	
	/**
	 * Removes given amount of energy from this Critter and checks if the Critter dies.
	 * @param eUsed the energy to be taken away.
	 * @return true if this Critter dies.
	 */
	private boolean useEnergy(int eUsed) {
		mem[4] -= eUsed;
		hasActed = true;
		if (mem[4] <= 0) {
			hex.removeCritter();
			hex.putFood(world.FOOD_PER_SIZE * mem[3]);
			world.removeCritter(this);
			return true;
		}
		return false;
	}
	
	/**
	 * Ensures that this Critter is ready for the next timestep of the simulation.
	 * @return true if the Critter is alive for the next turn
	 */
	public boolean getReady() {
		hasActed = false;
		if(isMating){
			isMating = false;
			return !useEnergy(mem[3]);
		}
		return true;
	}
	
	/**
	 * If possible, makes a baby Critter and puts it behind this Critter.
	 * @param partner the mating partner of this Critter.
	 * @return true if a baby was made.
	 */
	private boolean makeBaby(Critter partner) {
		isMating = false;
		partner.isMating = false;
		if (useEnergy(world.MATE_COST * complexity()))
			return false;
		if (partner.useEnergy(world.MATE_COST * partner.complexity()))
			return false;
		Location loc = hex.getLocation().nextLocation((dir + 3)%6);
		if (!loc.isValid())
			return false;
		Hex babyCrib = world.getHex(loc);
		if (babyCrib.isOccupied())
			return false;
		Random rnd = new Random();
		int[] earlyAttr = new int[3];
		for (int i = 0; i < 3; i++) {
			int parentChosen = rnd.nextInt(2);
			if (parentChosen == 0)
				earlyAttr[i] = mem[i];
			else
				earlyAttr[i] = partner.mem[i];
		}
		//TODO mix genomes and put Critter onto Hex.
		babyCrib.putCritter(new Critter(world, babyCrib, earlyAttr[0], earlyAttr[1], earlyAttr[2]));
		return true;
	}
	
	/**
	 * This Critter takes a randomly chosen action.
	 */
	public void takeRandomAction() {
		Random rnd = new Random();
		int action = rnd.nextInt(10);
		takeSpecifiedAction(action);
	}

	/**
	 * This Critter takes the specified Action, if Action is not one of the values
	 * defined in the constants in Critter, this Critter will rest.
	 * @param action the value of the action, defined by action constants in Critter.
	 */
	public void takeSpecifiedAction(int action) {
		switch(action) {
		case 1: forward(); break;
		case 2: backward(); break;
		case 3: left(); break;
		case 4: right(); break;
		case 5: eat(); break;
		case 6: attack(); break;
		case 7: grow(); break;
		case 8: bud(); break;
		case 9: mate(); break;
		default: rest(); break;
		}
	}

}
